﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tucil4Kripto
{
    class algorithm
    {
        //atribute
        private static int blocksize = 64; // in byte
        private static int sizemsgLength = 8; // 8 byte = 64 bit
        public static UInt32[] H;
        public static UInt32[] K;
        public static byte[] ABCDE;
        public static byte[] H1234;
        
        //METHODS
        /// <summary>
        /// prosedur untuk mendapatkan N byte message dari parameter message
        /// </summary>
        /// <param name="bytes">array of byte yang mau diambil</param>
        /// <param name="offset">indeks mulai message yang mau diambil</param>
        /// <param name="length">length menyatakan panjang dalam byte</param>
        /// <returns>N byte message dari parameter dimulai dari posisi ke offset</returns>
        public static byte[] getNbyteKey(byte[] bytes, long offset, long length)
        {
            byte[] result = new byte[length];
            for (long i = 0; i < length; ++i)
            {
                result[i] = bytes[offset + i];
            }
            return result;
        }

        //METHODS
        /// <summary>
        /// prosedur untuk mendapatkan N byte message dari parameter message
        /// </summary>
        /// <param name="bytes">array of byte yang mau diambil</param>
        /// <param name="offset">indeks mulai message yang mau diambil</param>
        /// <param name="length">length menyatakan panjang dalam byte</param>
        /// <returns>N byte message dari parameter dimulai dari posisi ke offset</returns>
        public static UInt32 getUInt32FromNByte(byte[] bytes, long offset, long length)
        {
            UInt32 result = bytes[offset];
            for (long i = 1; i < length; ++i)
            {
                result = (result << 8) + bytes[offset + i];
            }
            return result;
        }

        /// <summary>
        /// XOR prosedur untuk xor 2 array of 8 byte
        /// </summary>
        /// <param name="key1">key1 untuk xor</param>
        /// <param name="key2">key2 untuk xor</param>
        /// <param name="keylength">keylength menyatakan panjang kunci dalam byte</param>
        /// <returns>array of 8 byte from xor-ing key1 and key2</returns>
        public static byte[] XorNBitKey(byte[] key1, byte[] key2, long keylength)
        {
            byte[] result = new byte[keylength];
            for (long i = 0; i < keylength; ++i)
            {
                result[i] = (byte)((int)key1[i] ^ (int)key2[i]);
            }
            return result;
        }
        
        /// <summary>
        /// breaking a long (64bit) var into 8 byte array (@8bit)
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static byte[] breakLongIntoArrByte(long source, int CounterArray)
        {
            long filesize = source;
            byte[] retval = new byte[CounterArray];

            for (int i = 0; i < CounterArray; ++i)
            {
                retval[CounterArray - i - 1] = (byte)(filesize & 255);
                filesize = filesize >> 8;
            }
            return retval;
        }

        /// <summary>
        /// insert padding and message length into real_message
        /// </summary>
        /// <param name="message">true message</param>
        /// <returns></returns>
        public static byte[] insertPaddingAndMsgLength(byte[] message)
        {
            long len = message.LongLength;
            byte[] message_result;
            byte[] filelen = new byte[sizemsgLength];                           //normal file size
            filelen = breakLongIntoArrByte(message.LongLength * 8, sizemsgLength);

            int paddinglength = blocksize - (int)(len % blocksize);

            if (paddinglength < 9)
            {
                paddinglength += blocksize; // padding lengthnya harus nyisain paling gak 8 byte buat message length + 1 byte buat padding asal yg 128 --> 1xxx
            }

            message_result = new byte[len + paddinglength];
            Array.Copy(message, message_result, len);

            // insert padding max 512bit
            message_result[len] = 128;
            for (long i = 1; i < paddinglength - sizemsgLength; ++i)
            {
                message_result[len + i] = 0;
            }

            // insert msg length 64bit = 8byte
            for (long i = (paddinglength - sizemsgLength); i < paddinglength; ++i)
            {
                message_result[len + i] = filelen[i - (paddinglength - sizemsgLength)];
            }

            return message_result;
        }

        /// <summary>
        /// rotate bits left
        /// </summary>
        /// <param name="source"></param>
        /// <param name="shifting"></param>
        /// <returns></returns>
        public static UInt32 ROTL(UInt32 source, byte shifting)
        {
            return (UInt32)(((source) << (shifting)) | ((source) >> (32 - (shifting))));
        }

        /// <summary>
        /// get 160bit (20byte) of msg digest from Nx512bit msg data
        /// </summary>
        /// <param name="message">msg to find its msg digest</param>
        /// <returns></returns>
        public static byte[] getMessageDigest(byte[] message)
        {            
            long step = 0;
            byte[] result = new byte[20];
            UInt32[] working_abcde = new UInt32[5];

            //for i = 1 to N-512 bit msg
            while (step < message.LongLength)
            {
                UInt32[] Wt = new UInt32[80]; // message schedule
                UInt32 T = new UInt32();
                
                //initialize Wt
                for (int t = 0; t < 16; ++t)
                {
                    Wt[t] = getUInt32FromNByte(message, step + t * 4, 4);
                }
                for (int t = 16; t < 80; ++t)
                {
                    Wt[t] = ROTL((Wt[t - 3] ^ Wt[t - 8] ^ Wt[t - 14] ^ Wt[t - 16]), 1);
                }


                //initialize 5 working variable a, b, c, d, e with hash value from previous step
                for (int i = 0; i < 5; ++i)
                {
                    working_abcde[i] = H[i];
                }

                /*------------------------
                MAIN. 80loops
                ------------------------*/
                int indexKForUse = 0;
                // loop 0 .. 19

                // fungsi logika pada setiap putaran
                UInt32[] function = new UInt32[4];
                
                for (int t = 0; t < 80; ++t)
                {
                    function[0] = (working_abcde[1] & working_abcde[2]) ^ (~working_abcde[1] & working_abcde[3]);
                    function[1] = (working_abcde[1] ^ working_abcde[2] ^ working_abcde[3]);
                    function[2] = (working_abcde[1] & working_abcde[2]) ^ (working_abcde[1] & working_abcde[3]) ^ (working_abcde[2] & working_abcde[3]);
                    function[3] = (working_abcde[1] ^ working_abcde[2] ^ working_abcde[3]);

                    indexKForUse = t / 20; //putaran 1 0..19 = function[0], dst
                    T = ROTL(working_abcde[0], 5) + function[indexKForUse] + working_abcde[4] + K[indexKForUse] + Wt[t];
                    //setup new working memory
                    working_abcde[4] = working_abcde[3];
                    working_abcde[3] = working_abcde[2];
                    working_abcde[2] = ROTL(working_abcde[1], 30);
                    working_abcde[1] = working_abcde[0];
                    working_abcde[0] = T;
                }

                //compute the i-th intermediate hash value Hi
                for (int i = 0; i < 5; ++i)
                {
                    H[i] = working_abcde[i] + H[i];
                }

                step += blocksize;
            }

            //resulting 160-bit message digest of 'message'
            for (int i = 0; i < 5; ++i)
            {
                byte[] tempresult = breakLongIntoArrByte(H[i], 4);
                for (int j = 0; j < 4; ++j)
                {
                    result[i * 4 + j] = tempresult[j];
                }
            }

            return result;
        }
    }
}
